﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using AliyunOpenSearch4Net.App;
using AliyunOpenSearch4Net.Base;
using AliyunOpenSearch4Net.Data;
using AliyunOpenSearch4Net.Error;
using AliyunOpenSearch4Net.Index;
using AliyunOpenSearch4Net.Search;
using AliyunOpenSearch4Net.Suggest;

namespace AliyunOpenSearch4Net
{
    public partial class AliyunClient
    {
        private async Task<WebRequest> CreateWebRequestAsync(RequestBase request, string path, string method)
        {
            var body = string.Empty;

            var httpRequest = CreatePreparedWebRequest(request, path, method, out body);

            if (httpRequest.Method == HttpMethod.POST) // if the method is POST, write request body
            {
                httpRequest.ContentType = "application/x-www-form-urlencoded";

                var stream = await httpRequest.GetRequestStreamAsync();

                WriteRequestBody(stream, body);
            }

            return httpRequest;
        }

        internal async Task<TResponse> GetResponseAsync<TRequest, TResponse>(TRequest request, string path, string method)
            where TRequest : RequestBase<TResponse>
            where TResponse : Response, new()

        {
            var httpRequest = await CreateWebRequestAsync(request, path, method);

            var response = await httpRequest.GetResponseAsync();

            using (var stream = response.GetResponseStream())
            using (var reader = new StreamReader(stream, Encoding.UTF8, false))
            {
                var data = reader.ReadToEnd();
                return DeserializeObject<TResponse>(data);
            };
        }

        #region app operation

        public async Task<Response<AppStatusResult>> GetAppStatusAsync(string appName)
        {
            var request = new AppStatusRequest
            {
                AppName = appName
            };

            return await GetResponseAsync<AppStatusRequest, Response<AppStatusResult>>(request, "/index/" + appName, HttpMethod.GET);
        }

        public async Task<Response<int>> DeleteAppAsync(string appName)
        {
            var request = new AppDeleteRequest
            {
                AppName = appName
            };

            return await GetResponseAsync<AppDeleteRequest, Response<int>>(request, "/index/" + appName, HttpMethod.POST);
        }

        public async Task<Response<AppCreateResult>> CreateAppAsync(string appName, string template)
        {
            if (string.IsNullOrEmpty(appName))
                throw new ArgumentNullException("appName");

            if (string.IsNullOrEmpty(template))
                throw new ArgumentNullException("template");

            var request = new AppCreateRequest
            {
                AppName = appName,
                Template = template
            };

            return await GetResponseAsync<AppCreateRequest, Response<AppCreateResult>>(request, "/index/" + appName, HttpMethod.POST);
        }

        public async Task<AppListResponse> ListAppAsync(int page, int pageSize)
        {
            var request = new AppListRequest
            {
                Page = page,
                PageSize = pageSize
            };

            return await GetResponseAsync<AppListRequest, AppListResponse>(request, "/index", HttpMethod.GET);
        }

        #endregion

        #region data operation

        public async Task<Response> UploadDataAsync(string appName, string tableName, IList<DataItem> items)
        {
            var request = new DataPushRequest
            {
                TableName = tableName,
                Items = items
            };

            return await GetResponseAsync<DataPushRequest, Response>(request, "/index/doc/" + appName, HttpMethod.POST);
        }

        #endregion

        #region error

        public async Task<Response<ErrorListResult>> GetErrorsAsync(string appName, int page, int pageSize, SortMode sort)
        {
            var request = new ErrorListRequest
            {
                Page = page,
                PageSize = pageSize,
                SortMode = sort
            };

            return await GetResponseAsync<ErrorListRequest, Response<ErrorListResult>>(request, "/index/error/" + appName, HttpMethod.GET);
        }

        #endregion

        #region suggestions

        public async Task<SuggestResponse> GetSuggestionsAsync(string appName, string query, string suggestName, int hint)
        {
            var request = new SuggestRequest
            {
                IndexName = appName,
                Query = query,
                SuggestName = suggestName,
                Hint = hint
            };

            return await GetResponseAsync<SuggestRequest, SuggestResponse>(request, "/suggest", HttpMethod.GET);
        }

        #endregion

        #region index

        public async Task<Response> RebuildIndexAsync(string appName, string tableName, string operate)
        {
            var request = new IndexRebuildRequest
            {
                TableName = tableName,
                Operate = operate
            };

            return await GetResponseAsync<IndexRebuildRequest, Response>(request, "/index/" + appName, HttpMethod.GET);
        }

        #endregion

        #region search

        public async Task<Response<SearchResult>> SearchAsync(string appName, string query, string fetchFields = null, bool disabled = false, string firstFormulaName = null, string formulaName = null, string qp = null, SearchSummary summary = null)
        {
            var request = new SearchRequest
            {
                IndexName = appName,
                Query = query,
                FetchFields = fetchFields,
                Disable = disabled,
                FirstFormulaName = firstFormulaName,
                FormulaName = formulaName,
                QP = qp,
                Summary = summary
            };

            return await GetResponseAsync<SearchRequest, Response<SearchResult>>(request, "/search", HttpMethod.GET);
        }

        public async Task<Response<ScanResult>> ScanAsync(string appName, string query, string scroll, string searchType = null, string scrollId = null)
        {
            var request = new ScanRequest
            {
                IndexName = appName,
                Query = query,
                Scroll = scroll,
                ScrollId = scrollId,
                SearchType = searchType
            };

            return await GetResponseAsync<ScanRequest, Response<ScanResult>>(request, "/search", HttpMethod.GET);
        }

        #endregion
    }
}
